<!DOCTYPE html>
<html>
<head>
<title>周志华《机器学习》课后习题解答系列（六）：Ch5.10 - 卷积神经网络实验</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
/* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */
/* Author: Nicolas Hery - http://nicolashery.com */
/* Version: b13fe65ca28d2e568c6ed5d7f06581183df8f2ff */
/* Source: https://github.com/nicolahery/markdownpad-github */

/* RESET
=============================================================================*/

html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
}

/* BODY
=============================================================================*/

body {
  font-family: Helvetica, arial, freesans, clean, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: #333;
  background-color: #fff;
  padding: 20px;
  max-width: 960px;
  margin: 0 auto;
}

body>*:first-child {
  margin-top: 0 !important;
}

body>*:last-child {
  margin-bottom: 0 !important;
}

/* BLOCKS
=============================================================================*/

p, blockquote, ul, ol, dl, table, pre {
  margin: 15px 0;
}

/* HEADERS
=============================================================================*/

h1, h2, h3, h4, h5, h6 {
  margin: 20px 0 10px;
  padding: 0;
  font-weight: bold;
  -webkit-font-smoothing: antialiased;
}

h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
  font-size: inherit;
}

h1 {
  font-size: 28px;
  color: #000;
}

h2 {
  font-size: 24px;
  border-bottom: 1px solid #ccc;
  color: #000;
}

h3 {
  font-size: 18px;
}

h4 {
  font-size: 16px;
}

h5 {
  font-size: 14px;
}

h6 {
  color: #777;
  font-size: 14px;
}

body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
  margin-top: 0;
  padding-top: 0;
}

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
  margin-top: 0;
  padding-top: 0;
}

h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
  margin-top: 10px;
}

/* LINKS
=============================================================================*/

a {
  color: #4183C4;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* LISTS
=============================================================================*/

ul, ol {
  padding-left: 30px;
}

ul li > :first-child, 
ol li > :first-child, 
ul li ul:first-of-type, 
ol li ol:first-of-type, 
ul li ol:first-of-type, 
ol li ul:first-of-type {
  margin-top: 0px;
}

ul ul, ul ol, ol ol, ol ul {
  margin-bottom: 0;
}

dl {
  padding: 0;
}

dl dt {
  font-size: 14px;
  font-weight: bold;
  font-style: italic;
  padding: 0;
  margin: 15px 0 5px;
}

dl dt:first-child {
  padding: 0;
}

dl dt>:first-child {
  margin-top: 0px;
}

dl dt>:last-child {
  margin-bottom: 0px;
}

dl dd {
  margin: 0 0 15px;
  padding: 0 15px;
}

dl dd>:first-child {
  margin-top: 0px;
}

dl dd>:last-child {
  margin-bottom: 0px;
}

/* CODE
=============================================================================*/

pre, code, tt {
  font-size: 12px;
  font-family: Consolas, "Liberation Mono", Courier, monospace;
}

code, tt {
  margin: 0 0px;
  padding: 0px 0px;
  white-space: nowrap;
  border: 1px solid #eaeaea;
  background-color: #f8f8f8;
  border-radius: 3px;
}

pre>code {
  margin: 0;
  padding: 0;
  white-space: pre;
  border: none;
  background: transparent;
}

pre {
  background-color: #f8f8f8;
  border: 1px solid #ccc;
  font-size: 13px;
  line-height: 19px;
  overflow: auto;
  padding: 6px 10px;
  border-radius: 3px;
}

pre code, pre tt {
  background-color: transparent;
  border: none;
}

kbd {
    -moz-border-bottom-colors: none;
    -moz-border-left-colors: none;
    -moz-border-right-colors: none;
    -moz-border-top-colors: none;
    background-color: #DDDDDD;
    background-image: linear-gradient(#F1F1F1, #DDDDDD);
    background-repeat: repeat-x;
    border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
    border-image: none;
    border-radius: 2px 2px 2px 2px;
    border-style: solid;
    border-width: 1px;
    font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
    line-height: 10px;
    padding: 1px 4px;
}

/* QUOTES
=============================================================================*/

blockquote {
  border-left: 4px solid #DDD;
  padding: 0 15px;
  color: #777;
}

blockquote>:first-child {
  margin-top: 0px;
}

blockquote>:last-child {
  margin-bottom: 0px;
}

/* HORIZONTAL RULES
=============================================================================*/

hr {
  clear: both;
  margin: 15px 0;
  height: 0px;
  overflow: hidden;
  border: none;
  background: transparent;
  border-bottom: 4px solid #ddd;
  padding: 0;
}

/* TABLES
=============================================================================*/

table th {
  font-weight: bold;
}

table th, table td {
  border: 1px solid #ccc;
  padding: 6px 13px;
}

table tr {
  border-top: 1px solid #ccc;
  background-color: #fff;
}

table tr:nth-child(2n) {
  background-color: #f8f8f8;
}

/* IMAGES
=============================================================================*/

img {
  max-width: 100%
}
</style>
</head>
<body>
<p>本系列相关答案和源代码托管在我的Github上：<a href="https://github.com/PY131/Machine-Learning_ZhouZhihua">PY131/Machine-Learning_ZhouZhihua</a>.</p>
<h1>卷积神经网络实验 - 手写字符识别</h1>
<blockquote>
<p><img src="Ch5/5.10.png" /></p>
</blockquote>
<p>注：本题程实现基于python-theano（这里查看<a href="https://github.com/PY131/Machine-Learning_ZhouZhihua/tree/master/ch5_neural_networks/5.10_CNN">完整代码</a>和<a href="http://www-labs.iro.umontreal.ca/~lisa/deep/data/mnist/">数据集</a>）。</p>
<h2>1. 基础知识回顾</h2>
<h3>1.1. 核心思想</h3>
<p>卷积神经网络（Convolutional Neural Network, CNN）是“<strong>深度学习</strong>”的代表模型之一，是一种<strong>多隐层</strong>神经网络，正被广泛用于图像处理、语音识别等热点领域。</p>
<p>卷积神经网络的原理和特点，集中体现在以下三个核心思想当中：</p>
<ul>
<li><strong>局部感受野</strong>（Local Receptive Fields）</li>
<li><strong>权值共享</strong>（weight sharing）</li>
<li>时间或空间的<strong>亚采样</strong></li>
</ul>
<p>在整合了上述三大特点之后，卷积神经网络具备了很强的<strong>畸变容忍能力</strong>，能够从复杂的对象中<strong>隐式地<strong>进行</strong>特征提取与学习</strong>。  </p>
<h3>1.2. 结构和功能</h3>
<p>卷积神经网络同多层感知机（MLP）一样，通过设置多个隐层来实现对复杂模型的学习。如下图所示是一个手写字符识别的卷积神经网络结构示意图（书p114）：</p>
<p><img src="Ch5/5.10.CNN_sketch.png" /></p>
<p>从图中可以看到卷积层（convolutional layer）和采样层（pooling layer）的复合，其功能简述如下：</p>
<ul>
<li>卷积层包含多个<strong>特征映射</strong>（feature map），它们采用相应的<strong>卷积滤波器</strong>从输入中提取特征；</li>
<li>采样层基于<strong>局部相关性</strong>原理对卷积层进行<strong>亚采样</strong>，从而在保留有用信息的同时减少数据量；</li>
</ul>
<p>通过多层复合，隐层最终输出目标维<strong>特征向量</strong>，通过连接层和输出层输出结果。</p>
<h3>1.3. 参数技巧</h3>
<p>神经网络的参数设计十分重要，关于CNN模型的一些参数的考虑（如隐层特征图数目和大小、滤波器大小等），可参考<a href="http://deeplearning.net/tutorial/lenet.html#lenet">Convolutional Neural Networks (LeNet)</a>文章最后<strong>Tips and Tricks</strong>的内容。</p>
<h2>2. 手写字符识别实验</h2>
<p>这里，我们采用<strong>python-theano深度学习库</strong>来实现基于<a href="http://yann.lecun.com/exdb/mnist/">MNIST</a>数据的字符识别实验。关于theano的基础使用可参考：<a href="http://blog.csdn.net/snoopy_yuan/article/details/71548243">深度学习基础 - 基于Theano-MLP的字符识别实验（MNIST）</a>或是<a href="http://deeplearning.net/tutorial/">Deep Learning Tutorials</a>。</p>
<h3>2.1. 数据获取及预处理</h3>
<p>这里我们采用经过规约的数据集<a href="http://www-labs.iro.umontreal.ca/~lisa/deep/data/mnist/">mnist.pkl.gz</a>,给出该数据集的部分信息如下：</p>
<ul>
<li><strong>维度属性</strong>：数据集包含3个子数据集，对应train_set、valid_set、test_set，样本规模分别为50000、10000、10000；每条样本包含：输入向量[1*784]，对应输入图片灰度矩阵[28*28]；输出值，对应图片类别标签(数字0-9)；</li>
<li><strong>完整度</strong>：样本完整；</li>
<li><strong>平衡度</strong>：未知；</li>
<li><strong>更多信息</strong>：手写字符所覆盖的灰度已被人工调整到了图片的中部。</li>
</ul>
<p>下面是一些样例图片：</p>
<p><img src="Ch5/5.10.display_some.png" alt="样例图片" /></p>
<p>通过对数据集的分析，确定此处该数据集已无需额外的预处理即可使用，只是在使用时注意维度变换即可。</p>
<h3>2.2. 基于theano实现网络模型</h3>
<p>基于theano来训练一个卷积神经网络需要完成的内容包括：</p>
<ol>
<li><strong>参数初始化</strong>，采用<code>theano.shared</code>来达到权值共享，基于数据信息设计相关参数（隐层规模、滤波器大小、学习率、迭代次数限、若采样MSGD算法还需设置mini-batch大小等）；</li>
<li>相关<strong>辅助函数</strong>，如采用<code>theano.function</code>实现tanh/sigmoid、似然损失函数等；</li>
<li><strong>卷积</strong>操作（Convolution）和<strong>池化</strong>操作（pooling），采用<code>theano.tensor.signal.conv2d</code>实现二维（2D）卷积；采用<code>theano.tensor.signal.pool.pool_2d</code>实现最大池化（max-pooling），</li>
<li>训练<strong>过程优化机制</strong>，如加入不同时间尺度的验证、测试机制，早停机制；</li>
<li>实现迭代训练程序并得出模型（即最优参数）；</li>
</ol>
<p>进一步地：</p>
<ul>
<li>将卷积层与池化层（采样层）整个为一个复合层，称为<strong>卷积-池化层</strong>（<code>class LeNetConvPoolLayer</code>）；</li>
<li>将模型的训练、验证、测试整合在一个程序块中，方便早停判断；</li>
</ul>
<p>这里还需进一步说明各层规模和滤波器大小的设置：</p>
<p>以当前样本为例，输入层大小<code>[28*28]</code>，若采用<code>5*5</code>的滤波器进行卷积，则第一个卷积层的特征图大小为<code>[24*24]</code>（ps. 28-5+1=24），若紧接着的亚采样模版大小为<code>[2*2]</code>，那么该池化层特征图大小为<code>[12*12]</code>（ps. 24/2=12）。同理，可计算出下一个卷积池化的特征图大小为<code>[8*8]</code>和<code>[4*4]</code>，再往后就只需要一个面向连接层的一维卷积层了，其节点数为当前的<code>feature maps</code>数。然后按照MLP模型给出连接层和输出层即可。</p>
<p>各层规模设置的样例程序如下：</p>
<pre><code>```python
layer1 = LeNetConvPoolLayer(
    rng,
    input=layer0.output,
    image_shape=(batch_size, nkerns[0], 12, 12),
    filter_shape=(nkerns[1], nkerns[0], 5, 5),
    poolsize=(2, 2)
)    

layer2_input = layer1.output.flatten(2)

# construct a fully-connected sigmoidal layer
layer2 = HiddenLayer(
    rng,
    input=layer2_input,
    n_in=nkerns[1] * 4 * 4,
    n_out=500,
    activation=T.tanh
)

# classify the values of the fully-connected sigmoidal layer
layer3 = LogisticRegression(input=layer2.output, n_in=500, n_out=10)
```
</code></pre>

<p>给出该训练程序简化样例如下<a href="https://github.com/PY131/Machine-Learning_ZhouZhihua/blob/master/ch5_neural_networks/5.10_CNN/Mnist_CNN.py">查看完整程序</a>：</p>
<pre><code>```python
def evaluate_lenet5(learning_rate=0.1,      # 学习率
                    n_epochs=200,           # 迭代批数
                    dataset='mnist.pkl.gz', # 数据集文件
                    nkerns=[20, 50],        # 每隐层特征图数目序列
                    batch_size=500):        # mini-batch大小（for MSGD）

    # 加载数据，生成训练集/验证集/测试集
    datasets = load_data(dataset)

    train_set_x, train_set_y = datasets[0]
    valid_set_x, valid_set_y = datasets[1]
    test_set_x,  test_set_y  = datasets[2]
    ...
    # 搭建模型网络结构（包括上面的隐层sizes推导）

    # 输入
    layer0_input = x.reshape((batch_size, 1, 28, 28))

    # 第一层 - 复合
    layer0 = LeNetConvPoolLayer(
        rng,
        input=layer0_input,
        image_shape=(batch_size, 1, 28, 28),
        filter_shape=(nkerns[0], 1, 5, 5),
        poolsize=(2, 2)
    )

    # 第二层 - 复合
    layer1 = LeNetConvPoolLayer( ... ) 

    # 第三层 - 隐层   
    layer2_input = layer1.output.flatten(2)
    layer2 = HiddenLayer( ... )

    # 全连接输出   
    layer3 = LogisticRegression(input=layer2.output, n_in=500, n_out=10)

    # 似然损失函数
    cost = layer3.negative_log_likelihood(y)
    ...
    # 参数更新机制
    updates = [
        (param_i, param_i - learning_rate * grad_i)
        for param_i, grad_i in zip(params, grads)
    ]

    # 模型训练函数体
    train_model = theano.function(
        [index],
        cost,
        updates=updates,
        givens={
            x: train_set_x[index * batch_size: (index + 1) * batch_size],
            y: train_set_y[index * batch_size: (index + 1) * batch_size]
        }
    )

    ########## 模型训练 ##########

    # 早停机制设置
    patience = 10000  # 迭代次数耐心上限
    patience_increase = 2  # 耐心上限拓展步长
    improvement_threshold = 0.995  # 精度明显提升判断

    # 验证周期
    validation_frequency = min(n_train_batches, patience // 2)
    ...    
    # 循环
    while (epoch &lt; n_epochs) and (not done_looping):
        epoch = epoch + 1

        # mini-batch迭代
        for minibatch_index in range(n_train_batches):
            ...
            # 模型训练（损失计算+参数更新）
            cost_ij = train_model(minibatch_index)

            # 模型验证（a batch训练完成）
            if (iter + 1) % validation_frequency == 0:

                # 计算0-1损失 - 验证误差
                validation_losses = [validate_model(i) for i in range(n_valid_batches)]
                this_validation_loss = numpy.mean(validation_losses)
                ...

                # 如果取得更好模型（验证精度提升）
                if this_validation_loss &lt; best_validation_loss:
                    # 若精度提升明显，但耐心迭代次数上限达到，则提高迭代次数上限
                    if this_validation_loss &lt; best_validation_loss * improvement_threshold:
                        patience = max(patience, iter * patience_increase)
                    ...

                    # 进行测试（在验证精度提升时）以方便我们对比观测
                    test_losses = [
                        test_model(i)
                        for i in range(n_test_batches)
                    ]
                    test_score = numpy.mean(test_losses)
                    ...

            # 早停判断
            if patience &lt;= iter:
                done_looping = True
                break
        ...
    #返回所需信息

```
</code></pre>

<h3>2.3. 训练及测试结果</h3>
<p>这里采用MSGD（块随机梯度下降法）进行迭代寻优，下图是经过大约5万次迭代训练后得到的三种误差（训练/验证/测试）收敛曲线，可以看出其过程收敛性：</p>
<p><img src="Ch5/5.10.err_curve.png" alt="样例图片" /></p>
<p>显示出一些测试样本的预测结果如下图示：</p>
<p><img src="Ch5/5.10.display_tst.png" alt="样例图片" /></p>
<p>最终的运行结果打印如下:</p>
<pre><code># 最优验证误差结果
Best validation score of 1.080000 %
# 测试误差结果
Test performance 1.030000 %
# 过程时耗
The code for file Mnist_CNN.py ran for 90.23m
</code></pre>

<p>从这里的结果可以看出：一方面，卷积神经网络训练<strong>计算规模庞大</strong>（当前软硬件环境下耗时一个半小时，）；另一方面，得到的<strong>模型精度很高</strong>（在测试集上实现了约99%的精度，这基本意味着MNIST问题得到了解决）。</p>
<h2>3. 总结</h2>
<p>通过该实验，我们注意到：</p>
<ul>
<li>CNN是一种<strong>优秀</strong>的机器学习模型，能够实现较困难的学习任务；</li>
<li>以CNN为代表的“深度学习”模型的训练往往面临着<strong>巨大的计算量</strong>，为优化实现，一方面需要提升软硬件配置环境，另一方面要<strong>合理设计训练机制</strong>，包括MSGD、早停、正则化等辅助方法的合理运用；</li>
<li><strong>参数设置</strong>合理与否严重影响模型的训练效率和实现效果；</li>
</ul>
<p>通过该实验，我们回顾了<strong>卷积神经网络</strong>及其所代表的<strong>深度学习<strong>概念，练习了基于</strong>python-theano计算框架</strong>下的机器学习建模方法，为进一步的学习研究积累的实践经验。</p>
<h2>4. 参考</h2>
<p>下面列出相关参考：</p>
<ul>
<li>本文直接教程：<a href="http://blog.csdn.net/snoopy_yuan/article/details/71548243">深度学习基础 - 基于Theano-MLP的字符识别实验（MNIST）</a></li>
<li>本文直接教程：<a href="http://deeplearning.net/tutorial/lenet.html">Convolutional Neural Networks (LeNet)</a></li>
<li>一个有趣的可视化网站：<a href="http://scs.ryerson.ca/~aharley/vis/conv/">“CNN-数字识别”模型可视化</a></li>
<li>一个深度学习主页：<a href="http://deeplearning.net/tutorial/contents.html">Contents - DeepLearning 0.1 documentation</a></li>
</ul>

</body>
</html>
<!-- This document was created with MarkdownPad, the Markdown editor for Windows (http://markdownpad.com) -->
